﻿using System;
using System.Reflection;
using System.Diagnostics;

namespace Lilium.Control
{
	public enum EitherKind
	{
		Left,
		Right
	}

	public struct Either<TLeft, TRight>
	{
		public static Either<TLeft, TRight> MakeLeft(TLeft left)
		{
			return new Either<TLeft, TRight>
			{
				Kind = EitherKind.Left,
				Left = left,
			};
		}

		public static Either<TLeft, TRight> MakeRight(TRight right)
		{
			return new Either<TLeft, TRight>
			{
				Kind = EitherKind.Right,
				Right = right,
			};
		}

		public override string ToString()
		{
			switch (Kind)
			{
				case EitherKind.Left:
					return string.Format("Left: {0}", Left);
				case EitherKind.Right:
					return string.Format("Right: {0}", Right);
				default:
					return base.ToString();
			}
		}

		public static implicit operator Either<TLeft, TRight>(TRight value)
		{
			return Either<TLeft, TRight>.MakeRight(value);
		}

		public static implicit operator Either<TLeft, TRight>(TLeft value)
		{
			return Either<TLeft, TRight>.MakeLeft(value);
		}

		public EitherKind Kind { get; private set; }

		#region public TLeft Left

		private TLeft m_Left;

		public TLeft Left
		{
			[DebuggerNonUserCodeAttribute]
			get
			{
				if (Kind == EitherKind.Left)
					return m_Left;
				else
					throw new InvalidOperationException(string.Format("Unable to read left component of '{0}' Either", Kind));
			}
			[DebuggerNonUserCodeAttribute]
			private set
			{
				m_Left = value;
			}
		}

		#endregion

		#region public TRight Right

		private TRight m_Right;

		public TRight Right
		{
			[DebuggerNonUserCodeAttribute]
			get
			{
				if (Kind == EitherKind.Right)
					return m_Right;
				else
					throw new InvalidOperationException(string.Format("Unable to read right component of '{0}' Either", Kind));
			}
			[DebuggerNonUserCodeAttribute]
			private set
			{
				m_Right = value;
			}
		}

		#endregion
	}

	public static class EitherExtensions
	{
        public static Either<TLeft, TRight2> Bind<TLeft, TRight1, TRight2>(this Either<TLeft, TRight1> either, Func<TRight1, Either<TLeft, TRight2>> cont)
        {
            switch (either.Kind)
            {
                case EitherKind.Left:
                    return either.Left;
                case EitherKind.Right:
                    return cont(either.Right);
                default:
                    throw new InvalidOperationException(
                        string.Format("Non-exhaustive patterns in function {0}: unable to match Either's kind of {1}",
                        MethodBase.GetCurrentMethod().Name,
                        either.Kind));
            }
        }

		public static Either<TLeft, TRight1> If<TLeft, TRight, TRight1>(this TRight right, bool condition, Func<TRight, Either<TLeft, TRight1>> successBranch, Func<TRight, Either<TLeft, TRight1>> failBranch)
		{
			if (condition)
				return successBranch(right);
			else
				return failBranch(right);
		}

		public static Either<TLeft, TLeft> Left<TLeft>(this TLeft left)
		{
			return Either<TLeft, TLeft>.MakeLeft(left);
		}

		public static Either<TLeft, TRight> Left<TLeft, TRight>(this TLeft left)
		{
			return Either<TLeft, TRight>.MakeLeft(left);
		}

		public static Either<TRight, TRight> Right<TRight>(this TRight right)
		{
			return Either<TRight, TRight>.MakeRight(right);
		}

		public static Either<TLeft, TRight> Right<TLeft, TRight>(this TRight right)
		{
			return Either<TLeft, TRight>.MakeRight(right);
		}
		/*
		public static Either<TLeft, TRight> Select<TLeft, TRight>(this Either<TLeft, TRight> either, Action<TRight> action)
		{
			if (action == null) throw new ArgumentNullException("action");

			switch (either.Kind)
			{
				case EitherKind.Left:
					return either.Left;
				case EitherKind.Right:
					action(either.Right);
					return either.Right;
				default:
					throw new InvalidOperationException(
						string.Format("Non-exhaustive patterns in function {0}: unable to match Either's kind of {1}",
						MethodBase.GetCurrentMethod().Name,
						either.Kind));
			}
		}
		*/
		public static Either<TLeft, TRight1> Select<TLeft, TRight, TRight1>(this Either<TLeft, TRight> either, Func<TRight, TRight1> selector)
		{
			switch (either.Kind)
			{
				case EitherKind.Left:
					return either.Left;
				case EitherKind.Right:
					return selector(either.Right);
				default:
					throw new InvalidOperationException(
						string.Format("Non-exhaustive patterns in function {0}: unable to match Either's kind of {1}",
						MethodBase.GetCurrentMethod().Name,
						either.Kind));
			}
		}

		public static Either<TLeft, TRight2> SelectMany<TLeft, TRight, TRight1, TRight2>(this Either<TLeft, TRight> either, Func<TRight, Either<TLeft, TRight1>> continuation, Func<TRight, TRight1, TRight2> selector)
		{
			switch (either.Kind)
			{
				case EitherKind.Left:
					return either.Left;
				case EitherKind.Right:
					var either1 = continuation(either.Right);
					switch (either1.Kind)
					{
						case EitherKind.Left:
							return either1.Left;
						case EitherKind.Right:
							return selector(either.Right, either1.Right);
						default:
							throw new InvalidOperationException(
								string.Format("Non-exhaustive patterns in function {0}: unable to match Either's kind of {1}",
								MethodBase.GetCurrentMethod().Name,
								either1.Kind));
					}
					throw new NotImplementedException();
				default:
					throw new InvalidOperationException(
						string.Format("Non-exhaustive patterns in function {0}: unable to match Either's kind of {1}",
						MethodBase.GetCurrentMethod().Name,
						either.Kind));
			}
		}
	}
}